﻿using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using Yoozoo.Framework.Core;
using Yoozoo.Libs;
using Yoozoo.Managers;

namespace Gameplay.PVE.Survivor
{
    public class CollisionManager: BattleSingleton<CollisionManager>
    {
        public class CollisionEntity : IClass
        {
            public int unitId;
            public Vector3 position;
            public float radius;
            public void OnReset()
            {
                
            }
        }
        
        private class CollisionGrid : IClass
        {
            public Dictionary<int, CollisionEntity> entityMap = new Dictionary<int, CollisionEntity>();
            public void OnReset()
            {
                
            }
        }


        private class CollisionGroupDebugger : IClass
        {
            public EBulletShapeType shapeType;
            public Vector3 position;
            public Vector3 forward;
            public float radius;
            public float angle;
            public float width;
            public float height;
            public Dictionary<int,Vector3> targets = new Dictionary<int,Vector3>();

            public float startTime;
            public float endTime;

            public Bullet bullet;

            public void Init(EBulletShapeType shapeType,Vector3 position,Vector3 forward,float param1,float param2 = 0)
            {
                forward.y = 0;
                forward = forward.normalized;
                this.shapeType = shapeType;
                this.position = position;
                this.forward = forward;
                if (shapeType == EBulletShapeType.Circle)
                {
                    radius = param1;
                }
                else if (shapeType == EBulletShapeType.Sector)
                {
                    radius = param1;
                    angle = param2;
                }
                else if (shapeType == EBulletShapeType.Rect)
                {
                    width = param1;
                    height = param2;
                }
                startTime = TimeManager.time;
                endTime = TimeManager.time + 3;
            }
            
            public void SetTarget(List<int> targets)
            {
                this.targets.Clear();
                for (int i = 0; i < targets.Count; i++)
                {
                    var unit = UnitManager.Instance.GetUnitById(targets[i]);
                    if (unit != null)
                    {
                        this.targets.Add(unit.unitId,unit.Position);
                    }
                }
            }

            public void Draw()
            {
#if UNITY_EDITOR
                Gizmos.color = Color.green;
                //形状
                if (shapeType == EBulletShapeType.Circle)
                {
                    BattleHelper.DrawCircle(position,radius);
                }
                else if (shapeType == EBulletShapeType.Sector)
                {
                    BattleHelper.DrawSector(position,forward,angle,radius);
                }
                else if (shapeType == EBulletShapeType.Rect)
                {
                    BattleHelper.DrawRect(position,forward,width,height);
                }
                
                //目标
                foreach (var item in targets)
                {
                    var position = item.Value;
                    var id = item.Key;
                    UnityEditor.Handles.Label(position,item.Key.ToString());
                }
#endif
            }   
            
            public void OnReset()
            {
                
            }
        }
        
        private List<CollisionGrid> collisionGrids = new List<CollisionGrid>();
        private Dictionary<int,int> unitToGridIndexMap = new Dictionary<int, int>();
        
        private List<CollisionGroupDebugger> collisionGroupDebuggers = new List<CollisionGroupDebugger>();

        private float gridSize = 2;
        private float mapWidth = 200;
        private float mapHeight = 200;
        private int mapWidthCount;
        private int mapHeightCount;
        private Vector3 mapCenter = Vector3.zero;
        
        public void Initialize()
        {
            mapWidthCount = (int)(mapWidth / gridSize);
            mapHeightCount = (int)(mapHeight / gridSize);
        }

        public void Refresh(Vector3 center)
        {
            mapCenter = center;
            collisionGrids.Clear();
            for (int i = 0; i < mapWidthCount; i++)
            {
                for (int j = 0; j < mapHeightCount; j++)
                {
                    var grid = new CollisionGrid();
                    collisionGrids.Add(grid);
                }
            }

            var oldList = new List<int>();
            foreach (var entity in unitToGridIndexMap)
            {
                var unitId = entity.Key;
                oldList.Add(unitId);
            }
            unitToGridIndexMap.Clear();

            for (int i = 0; i < oldList.Count; i++)
            {
                var unitId = oldList[i];
                var unit = UnitManager.Instance.GetUnitById(unitId);
                if (unit != null)
                {
                    AddEntity(unitId,unit.Position,BattleConstValue.UnitCollisionRadius);
                }
            }
        }

        public CollisionEntity AddEntity(int unitId,Vector3 position,float radius)
        {
            var currentIndex = GetGridIndexByPosition(position);
            var entity = new CollisionEntity
            {
                unitId = unitId,
                position = position,
                radius = radius
            };
            collisionGrids[currentIndex].entityMap[unitId] = entity;
            unitToGridIndexMap[unitId] = currentIndex;
            return entity;
        }

        public void MoveEntity(int unitId,Vector3 position)
        {
            if (unitToGridIndexMap.TryGetValue(unitId,out var gridIndex))
            {
                var currentIndex = GetGridIndexByPosition(position);
                if (currentIndex == gridIndex)
                {
                    //无事发生
                    return;
                }
                
                var grid = collisionGrids[gridIndex];
                if (grid.entityMap.Remove(unitId, out var entity))
                {
                    entity.position = position;
                    collisionGrids[currentIndex].entityMap[unitId] = entity;
                    unitToGridIndexMap[unitId] = currentIndex;
                }
                else
                {
                    UnityEngine.Debug.LogError("原来的Grid " + gridIndex+ "找不到Entity " + unitId);
                }
            }
            else
            {
                UnityEngine.Debug.LogError("找不到Entity " + unitId);
            }
        }

        public void RemoveEntity(int unitId)
        {
            if (unitToGridIndexMap.TryGetValue(unitId, out var gridIndex))
            {
                var grid = collisionGrids[gridIndex];
                if (grid.entityMap.Remove(unitId))
                {
                    unitToGridIndexMap.Remove(unitId);
                }
                else
                {
                    UnityEngine.Debug.LogError("Grid " + gridIndex+ "找不到Entity " + unitId);
                }
            }
        }

        private int GetGridIndexByPosition(Vector3 position)
        {
            var halfWidth = mapWidth / 2;
            var halfHeight = mapHeight / 2;
            var x = (int)((position.x - (mapCenter.x - halfWidth)) / gridSize);
            var y = (int)((position.z - (mapCenter.z - halfHeight)) / gridSize);
            if (x < 0 || x >= mapWidthCount || y < 0 || y > mapHeightCount)
            {
                //UnityEngine.Debug.LogError("位置超出地图范围 " + position);
                return 0;
            }
            return x * mapHeightCount + y;
        }

        private int GetX(float worldX)
        {
            var halfWidth = mapWidth / 2;
            var offset = worldX - ( mapCenter.x - halfWidth);
            var x = (int)(offset / gridSize);
            x = Mathf.Clamp(x, 0, mapWidthCount - 1);
            return x;
        }

        private int GetZ(float worldZ)
        {
            var halfHeight = mapHeight / 2;
            var offset = worldZ - ( mapCenter.z - halfHeight);
            var z = (int)(offset / gridSize);
            z = Mathf.Clamp(z, 0, mapHeightCount - 1);
            return z;
        }

        private List<int> SearchTargetInCircle(Vector3 position,float radius)
        {
            var result = new List<int>();
            var gridLeft = GetX(position.x - radius);
            var gridRight = GetX(position.x + radius);
            var gridBottom = GetZ(position.z - radius);
            var gridTop = GetZ(position.z + radius);
            for (int i = gridLeft; i <= gridRight; i++)
            {
                for (int j = gridBottom; j <= gridTop; j++)
                {
                    var gridIndex = i * mapHeightCount + j;
                    if (gridIndex >= 0 && gridIndex < collisionGrids.Count)
                    {
                        var grid = collisionGrids[gridIndex];
                        foreach (var entity in grid.entityMap.Values)
                        {
                            var diff = position - entity.position;
                            var r = radius + entity.radius;
                            if (diff.sqrMagnitude <= r * r)
                            {
                                result.Add(entity.unitId);
                            }
                        }
                    }
                }
            }
            return result;
        }

        private List<int> SearchTargetInRect(Vector3 position,Vector3 forward,float width,float height)
        {
            forward = forward.normalized;
            var result = new List<int>();
            var halfWidth = width / 2;
            var halfHeight = height / 2;
            var leftForward = Quaternion.AngleAxis(-90, Vector3.up) * forward;
            position = position + forward * halfHeight;
            var p1 = position + leftForward * halfWidth + forward * halfHeight;
            var p2 = position - leftForward * halfWidth + forward * halfHeight;
            var p3 = position - leftForward * halfWidth - forward * halfHeight;
            var p4 = position + leftForward * halfWidth - forward * halfHeight;
            var gridLeft = GetX(Mathf.Min(p1.x, p2.x, p3.x, p4.x));
            var gridRight = GetX(Mathf.Max(p1.x, p2.x, p3.x, p4.x));
            var gridBottom = GetZ(Mathf.Min(p1.z, p2.z, p3.z, p4.z));
            var gridTop = GetZ(Mathf.Max(p1.z, p2.z, p3.z, p4.z));
            for (int i = gridLeft; i <= gridRight; i++)
            {
                for (int j = gridBottom; j <= gridTop; j++)
                {
                    var gridIndex = i * mapHeightCount + j;
                    if (gridIndex >= 0 && gridIndex < collisionGrids.Count)
                    {
                        var grid = collisionGrids[gridIndex];
                        foreach (var entity in grid.entityMap.Values)
                        {
                            var diffToWidth = BattleHelper.DistanceFromPoint2Line(entity.position, position - halfHeight * forward, position + halfHeight * forward);
                            var diffToHeight = BattleHelper.DistanceFromPoint2Line(entity.position, position - halfWidth * leftForward, position + halfWidth * leftForward);
                            if (diffToWidth <= (entity.radius + halfWidth) && diffToHeight <= (entity.radius + halfHeight))
                            {
                                result.Add(entity.unitId);
                            }
                        }
                    }
                }
            }
            return result;
        }

        private List<int> SearchTargetInSector(Vector3 position, Vector3 forward, float radius, float angle)
        {
            forward = forward.normalized;
            var result = new List<int>();
            //找到四个点
            var halfAngle = angle / 2;
            var leftForward = Quaternion.AngleAxis(-halfAngle, Vector3.up) * forward;
            var rightForward = Quaternion.AngleAxis(halfAngle, Vector3.up) * forward;
            var p1 = position;
            var p2 = position + leftForward * radius;
            var p3 = position + rightForward * radius;
            var p4 = position + forward * radius;
            var gridLeft = GetX(Mathf.Min(p1.x, p2.x, p3.x, p4.x));
            var gridRight = GetX(Mathf.Max(p1.x, p2.x, p3.x, p4.x));
            var gridBottom = GetZ(Mathf.Min(p1.z, p2.z, p3.z, p4.z));
            var gridTop = GetZ(Mathf.Max(p1.z, p2.z, p3.z, p4.z));
            for (int i = gridLeft; i <= gridRight; i++)
            {
                for (int j = gridBottom; j <= gridTop; j++)
                {
                    var gridIndex = i * mapHeightCount + j;
                    if (gridIndex >= 0 && gridIndex < collisionGrids.Count)
                    {
                        var grid = collisionGrids[gridIndex];
                        foreach (var entity in grid.entityMap.Values)
                        {
                            var diff = position - entity.position;
                            var r = radius + entity.radius;
                            if (diff.sqrMagnitude <= r * r)
                            {
                                //还得判断角度
                                var dir = entity.position - position;
                                var angleDiff = Vector3.Angle(forward, dir);
                                if (angleDiff <= halfAngle)
                                {
                                    result.Add(entity.unitId);
                                }
                                else
                                {
                                    //可能跟边缘擦着
                                    var distanceToLeft = BattleHelper.DistanceFromPoint2Line(entity.position, p1, p2);
                                    var distanceToRight = BattleHelper.DistanceFromPoint2Line(entity.position, p1, p3);
                                    if (distanceToLeft <= entity.radius || distanceToRight <= entity.radius)
                                    {
                                        result.Add(entity.unitId);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return result;
        }

        private List<int> emptyGroup = new List<int>();
        
        public List<int> GetCollisionGroup(Vector3 position, Vector3 forward, int shapeType,float param1, float param2 = 0)
        {
            List<int> result;
            if (shapeType == (int)EBulletShapeType.Circle)
            {
                var radius = param1;
                result = SearchTargetInCircle(position, radius);
            }
            else if (shapeType == (int)EBulletShapeType.Sector)
            {
                var radius = param1;
                var angle = param2;
                result = SearchTargetInSector(position, forward, radius, angle);
            }
            else if (shapeType == (int)EBulletShapeType.Rect)
            {
                var width = param1;
                var height = param2;
                result = SearchTargetInRect(position, forward, width, height);
            }
            else
            {
                result = emptyGroup;
                if (BattleDebug.enableLog)
                {
                    BattleDebug.LogError("没有实现的碰撞类型 " + shapeType);
                }
            }
            return result;
        }

        public void ShowBulletCollision(Bullet bullet,List<int> targetList,Vector3 position, Vector3 forward, int shapeType, float param1,
            float param2 = 0)
        {
            CollisionGroupDebugger group = null;
            for (int i = 0; i < collisionGroupDebuggers.Count; i++)
            {
                if (collisionGroupDebuggers[i].bullet == bullet)
                {
                    group = collisionGroupDebuggers[i];
                    break;
                }
            }

            if (group == null)
            {
                group = ClassManager.Get<CollisionGroupDebugger>();
                group.bullet = bullet;
            }
            group.Init((EBulletShapeType)shapeType,position,forward,param1,param2);
            group.SetTarget(targetList);
            collisionGroupDebuggers.Add(group);
        }

        private Ray ray = new Ray();
        private RaycastHit[] rayHits = new RaycastHit[10];
        
        public Vector3 GetRayCastTarget(Vector3 position,Vector3 forward,float distance,ref MapObstacleData hit)
        {
            hit = null;
            ray.origin = position;
            ray.direction = forward;
            var count = Physics.RaycastNonAlloc(ray, rayHits, distance,LayerMask.GetMask("SurvivorObstacle"));
            if (count > 0)
            {
                hit = rayHits[0].transform.GetComponent<MapObstacleData>();
                return rayHits[0].point;
            }
            return position + forward * distance;
        }

        public void DrawGizmos()
        {
            if (SurvivorManager.showCollision)
            {
                foreach (var item in unitToGridIndexMap)
                {
                    var unitId = item.Key;
                    var gridIndex = item.Value;
                    var grid = collisionGrids[gridIndex];
                    var entity = grid.entityMap[unitId];
                    Gizmos.color = Color.red;
                    BattleHelper.DrawCircle(entity.position,entity.radius);
                }
#if UNITY_EDITOR
                for (int i = 0; i < collisionGroupDebuggers.Count; i++)
                {
                    var group = collisionGroupDebuggers[i];
                    if (TimeManager.time > group.endTime)
                    {
                        ClassManager.Free(group);
                        collisionGroupDebuggers.RemoveAt(i);
                        i--;
                        continue;
                    }
                    group.Draw();
                }
#endif
            }
        }
        
        public override void Dispose()
        {
            collisionGrids.Clear();
            unitToGridIndexMap.Clear();
        }
    }
}
